import datetime
import imaplib
import smtplib
import email
import re
import os
from typing import Optional, Dict, List, Union
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders


class SingletonMeta(type):
    """Singleton metaclass to ensure only one instance of a class.

    作用说明：
    1. 元类是类的模板，通过重写__call__方法控制类的实例化过程，确保被修饰的类全局仅一个实例。
    2. 相比在类内部用__new__实现单例，元类的优势在于：
       - 可复用性：一次定义后可应用于多个类，减少代码冗余
       - 集中控制：单例逻辑统一维护，便于后续修改
       - 继承兼容性：子类无需额外处理即可继承单例特性
    """
    _instances = {}  # 存储类实例的缓存字典

    def __call__(cls, *args, **kwargs):
        """重写__call__方法以控制实例创建过程。

        Args:
            cls: 被实例化的类（例如 LoggingManager）
            *args: 类初始化时的位置参数
            **kwargs: 类初始化时的关键字参数

        Returns:
            类的唯一实例（若已创建则返回缓存实例）
        """
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]


class MailManager(metaclass=SingletonMeta):
    """基于单例模式的邮件管理类（支持收发邮件）
    
    功能说明：
    - 全局唯一实例，统一管理IMAP（接收）和SMTP（发送）连接
    - 支持接收指定发件人邮件、解析邮件内容
    - 支持发送文本/HTML格式邮件，可添加附件
    - 适配QQ邮箱（需使用授权码，而非登录密码）
    """
    _initialized = False  # 标记是否已初始化
    _mailids: List[bytes] = []  # 存储接收邮件的ID列表
    _imap_server: Optional[imaplib.IMAP4_SSL] = None  # IMAP接收服务器连接
    _smtp_server: Optional[smtplib.SMTP_SSL] = None  # SMTP发送服务器连接

    def __init__(
        self,
        mailuser: str = "295243105@qq.com",
        mailpass: str = "zmtrpgavcqbmcaci",  # QQ邮箱需用授权码
        imap_host: str = "imap.qq.com",
        imap_port: int = 993,
        smtp_host: str = "smtp.qq.com",
        smtp_port: int = 465
    ):
        """初始化邮件配置（仅首次实例化时执行）
        
        Args:
            mailuser: 邮箱账号（发件人地址）
            mailpass: 邮箱授权码（QQ邮箱需在设置中开启IMAP/SMTP并获取）
            imap_host: IMAP接收服务器地址（默认QQ邮箱）
            imap_port: IMAP接收服务器端口（默认993，SSL）
            smtp_host: SMTP发送服务器地址（默认QQ邮箱）
            smtp_port: SMTP发送服务器端口（默认465，SSL）
        """
        if not self._initialized:
            self.mailuser = mailuser
            self.mailpass = mailpass
            self.imap_host = imap_host
            self.imap_port = imap_port
            self.smtp_host = smtp_host
            self.smtp_port = smtp_port

            # 初始化IMAP（接收）和SMTP（发送）连接
            self._init_imap()
            self._init_smtp()

            self._initialized = True

    def _init_imap(self) -> None:
        """初始化IMAP接收连接并登录（内部方法）"""
        try:
            self._imap_server = imaplib.IMAP4_SSL(self.imap_host, self.imap_port)
            result, _ = self._imap_server.login(self.mailuser, self.mailpass)
            if result == "OK":
                print(f"IMAP接收连接已初始化（账号：{self.mailuser}）")
            else:
                print(f"IMAP登录失败，状态码: {result}")
        except imaplib.IMAP4.error as e:
            print(f"IMAP初始化异常: {str(e)}")


    def _init_smtp(self) -> None:
        """初始化SMTP发送连接并登录（内部方法）"""
        try:
            self._smtp_server = smtplib.SMTP_SSL(self.smtp_host, self.smtp_port)
            self._smtp_server.login(self.mailuser, self.mailpass)
            print(f"SMTP发送连接已初始化（账号：{self.mailuser}）")
        except smtplib.SMTPException as e:
            print(f"SMTP初始化异常: {str(e)}")

    # ------------------------------
    # 接收邮件相关方法
    # ------------------------------
    def _read_mail_ids(self, sender: str = "599941994@qq.com") -> None:
        """读取指定发件人的邮件ID列表（内部方法）"""
        if not self._imap_server:
            print("IMAP连接未初始化，无法读取邮件")
            return

        try:
            status, _ = self._imap_server.select("INBOX")  # 选择收件箱
            if status != "OK":
                print("无法选择收件箱")
                return

            # 搜索指定发件人的邮件（支持模糊匹配，精确匹配用"FROM {sender}"）
            status, mailids = self._imap_server.search(None, f"FROM {sender}")
            if status == "OK":
                self._mailids = mailids[0].split()
                print(f"成功读取 {len(self._mailids)} 封来自 {sender} 的邮件")
            else:
                print("读取邮件ID失败")
        except Exception as e:
            print(f"读取邮件ID异常: {str(e)}")

    def _parse_mail_content(self, mail_id: bytes) -> Dict[str, str]:
        """解析单封邮件内容（内部方法）"""
        result = {}
        if not self._imap_server:
            print("IMAP连接未初始化，无法解析邮件")
            return result

        try:
            status, bytedata = self._imap_server.fetch(mail_id, "(RFC822)")
            if status != "OK":
                print(f"读取邮件 {mail_id} 内容失败")
                return result

            _, datatext = bytedata[0]
            mail_object = email.message_from_bytes(datatext)

            # 提取发件人邮箱
            from_match = re.search(r"<(?P<mail>.*?)>", mail_object["from"])
            result["from"] = from_match.group("mail") if from_match else mail_object["from"]

            # 提取收件人邮箱
            to_match = re.search(r"<(?P<mail>.*?)>", mail_object["to"])
            result["to"] = to_match.group("mail") if to_match else mail_object["to"]

            # 格式化日期
            try:
                date_obj = datetime.datetime.strptime(
                    mail_object["date"],
                    "%a, %d %b %Y %H:%M:%S %z"
                )
                result["date"] = date_obj.strftime("%Y-%m-%d %H:%M:%S")
            except ValueError:
                result["date"] = mail_object["date"]  # 保留原始格式

            # 提取文本内容（优先纯文本，无则尝试HTML）
            result["text"] = ""
            for part in mail_object.walk():
                content_type = part.get_content_type()
                if content_type == "text/plain":
                    body = part.get_payload(decode=True)
                    if body:
                        result["text"] = body.decode("utf-8", errors="replace")
                    break
                elif content_type == "text/html" and not result["text"]:
                    body = part.get_payload(decode=True)
                    if body:
                        result["text"] = f"[HTML内容] {body.decode('utf-8', errors='replace')[:200]}..."  # 简略显示

            print(f"邮件 {mail_id} 内容解析成功")
        except Exception as e:
            print(f"解析邮件异常: {str(e)}")
        
        return result

    # ------------------------------
    # 发送邮件相关方法
    # ------------------------------
    def _send_mail(
        self,
        to_addrs: Union[str, List[str]],
        subject: str,
        content: str,
        is_html: bool = False,
        attachments: Optional[List[str]] = None
    ) -> bool:
        """发送邮件（内部方法）
        
        Args:
            to_addrs: 收件人地址（单个字符串或列表）
            subject: 邮件主题
            content: 邮件内容
            is_html: 是否为HTML格式内容（默认False，文本格式）
            attachments: 附件路径列表（可选）
            
        Returns:
            发送成功返回True，失败返回False
        """
        if not self._smtp_server:
            print("SMTP连接未初始化，无法发送邮件")
            return False

        # 处理收件人格式（确保是列表）
        if isinstance(to_addrs, str):
            to_addrs = [to_addrs]

        # 创建邮件对象
        msg = MIMEMultipart()
        msg["From"] = self.mailuser
        msg["To"] = ", ".join(to_addrs)  # 多个收件人用逗号分隔
        msg["Subject"] = subject

        # 添加正文内容
        content_type = "text/html" if is_html else "text/plain"
        msg.attach(MIMEText(content, content_type, "utf-8"))

        # 添加附件
        if attachments:
            for file_path in attachments:
                if not os.path.exists(file_path):
                    print(f"附件不存在：{file_path}，已跳过")
                    continue

                try:
                    with open(file_path, "rb") as f:
                        part = MIMEBase("application", "octet-stream")
                        part.set_payload(f.read())
                    encoders.encode_base64(part)  # 编码为base64
                    # 添加附件头（包含文件名）
                    filename = os.path.basename(file_path)
                    part.add_header(
                        "Content-Disposition",
                        f"attachment; filename= {filename}",
                    )
                    msg.attach(part)
                    print(f"已添加附件：{filename}")
                except Exception as e:
                    print(f"添加附件 {file_path} 失败：{str(e)}")

        # 发送邮件
        try:
            self._smtp_server.sendmail(self.mailuser, to_addrs, msg.as_string())
            print(f"邮件发送成功（收件人：{', '.join(to_addrs)}）")
            return True
        except smtplib.SMTPException as e:
            print(f"邮件发送失败：{str(e)}")
            return False

    # ------------------------------
    # 公开类方法（对外接口）
    # ------------------------------
    @classmethod
    def read_mail_ids(cls, sender: str = "599941994@qq.com") -> None:
        """读取指定发件人的邮件ID列表"""
        cls()._read_mail_ids(sender)

    @classmethod
    def get_latest_mail(cls) -> Dict[str, str]:
        """获取最新一封邮件的内容"""
        instance = cls()
        if not instance._mailids:
            print("未获取到邮件ID，请先调用read_mail_ids方法")
            return {}
        return instance._parse_mail_content(instance._mailids[-1])

    @classmethod
    def get_mail_by_index(cls, index: int) -> Dict[str, str]:
        """通过索引获取指定邮件（0为最早，-1为最新）"""
        instance = cls()
        if not instance._mailids:
            print("未获取到邮件ID，请先调用read_mail_ids方法")
            return {}
        
        try:
            mail_id = instance._mailids[index]
            return instance._parse_mail_content(mail_id)
        except IndexError:
            print(f"索引 {index} 超出范围，共 {len(instance._mailids)} 封邮件")
            return {}

    @classmethod
    def send_mail(
        cls,
        to_addrs: Union[str, List[str]],
        subject: str,
        content: str,
        is_html: bool = False,
        attachments: Optional[List[str]] = None
    ) -> bool:
        """发送邮件（支持文本/HTML/附件）
        
        Args:
            to_addrs: 收件人地址（单个字符串或列表）
            subject: 邮件主题
            content: 邮件内容
            is_html: 是否为HTML格式（默认False）
            attachments: 附件路径列表（可选）
            
        Returns:
            发送成功返回True，失败返回False
        """
        return cls()._send_mail(to_addrs, subject, content, is_html, attachments)

    @classmethod
    def close_connections(cls) -> None:
        """关闭IMAP和SMTP连接"""
        instance = cls()
        # 关闭IMAP连接
        if instance._imap_server:
            try:
                instance._imap_server.close()
                instance._imap_server.logout()
                print("IMAP接收连接已关闭")
            except Exception as e:
                print(f"关闭IMAP连接异常: {str(e)}")
        # 关闭SMTP连接
        if instance._smtp_server:
            try:
                instance._smtp_server.quit()
                print("SMTP发送连接已关闭")
            except Exception as e:
                print(f"关闭SMTP连接异常: {str(e)}")